/* CoralCube | Massively Multiplayer Online Role-Playing Game(MMORPG) Emulator.
|* 
|* Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
|*               2008-2010 TrinityCore <http://www.trinitycore.org/>
|*               2008-2010 CoralCube <http://www.CoralCube.com/>
|*
|* This program is free software. You can redistribute it and/or modify it under the terms of 
|* the GNU General Public License as published by the Free Software Foundation, either 
|* version 2 of the License.
|*
|* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, 
|* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
|* See the GNU General Public License for more details.
|*
|* You should have received a copy of the GNU General Public License along with this program. 
|* If not, see <http://www.gnu.org/licenses/>.
|*/

#include <ace/Sig_Handler.h>

#include "Common.h"
#include "SystemConfig.h"
#include "SignalHandler.h"
#include "World.h"
#include "WorldRunnable.h"
#include "WorldSocket.h"
#include "WorldSocketMgr.h"
#include "Configuration/Config.h"
#include "Database/DatabaseEnv.h"
#include "Database/DatabaseWorkerPool.h"

#include "CliRunnable.h"
#include "Log.h"
#include "Master.h"
#include "RARunnable.h"
#include "RCSoap.h"
#include "Timer.h"
#include "Util.h"
#include "BigNumber.h"

#ifdef _WIN32
#include "ServiceWin32.h"
extern int m_ServiceStatus;
#endif

/// Handle cored's termination signals
class CoredSignalHandler : public Core::SignalHandler
{
public:
    virtual void HandleSignal(int SigNum)
    {
        switch (SigNum)
        {
        case SIGINT:
            World::StopNow(RESTART_EXIT_CODE);
            break;
        case SIGTERM:
#ifdef _WIN32
        case SIGBREAK:
            if (m_ServiceStatus != 1)
#endif /* _WIN32 */
                World::StopNow(SHUTDOWN_EXIT_CODE);
            break;
        }
    }
};

class FreezeDetectorRunnable : public ACE_Based::Runnable
{
public:
    FreezeDetectorRunnable() { _delaytime = 0; }
    uint32 m_loops, m_lastchange;
    uint32 w_loops, w_lastchange;
    uint32 _delaytime;
    void SetDelayTime(uint32 t) { _delaytime = t; }
    void run(void)
    {
        if (!_delaytime)
            return;

        sLog.outString("Starting up anti-freeze thread (%u seconds max stuck time)...", _delaytime/1000);

        m_loops = 0;
        w_loops = 0;
        m_lastchange = 0;
        w_lastchange = 0;
        while (!World::IsStopped())
        {
            ACE_Based::Thread::Sleep(1000);
            uint32 curtime = getMSTime();
            // normal work
            if (w_loops != World::m_worldLoopCounter)
            {
                w_lastchange = curtime;
                w_loops = World::m_worldLoopCounter;
            }
            // possible freeze
            else if (getMSTimeDiff(w_lastchange,curtime) > _delaytime)
            {
                sLog.outError("World Thread hangs, kicking out server!");
                *((uint32 volatile*)NULL) = 0;                       // bang crash
            }
        }
        sLog.outString("Anti-freeze thread exiting without problems.");
    }
};

Master::Master() { }
Master::~Master() { }

/// Main function
int Master::Run()
{
    BigNumber seed1;
    seed1.SetRand(16 * 8);

    sLog.outString("%s (WorldServer-Daemon)", _FULLVERSION);
    sLog.outString("<Ctrl-C> to stop.");
    sLog.outString();
    sLog.outString("                     __ ");
    sLog.outString("                    /\\_\\ ");
    sLog.outString("         ___________\\/_/_  ________     _______  __    __ ");
    sLog.outString("        /\\__   ____ `\\/\\ \\/\\   ____`\\ /\\`  __  \\/\\ `\\ /\\ \\ ");
    sLog.outString("        \\/_/\\  \\__/\\  \\ \\ \\ \\  \\__/\\ \\\\ \\  \\/\\  \\ \\  `\\ \\ \\ ");
    sLog.outString("           \\ \\  \\_\\/  /\\ \\ \\ \\  \\_\\/ /_\\ \\  \\ \\  \\ \\ \\ `\\\\ \\ ");
    sLog.outString("            \\ \\   ___, `\\ \\ \\ \\   ___, `\\ \\  \\ \\  \\ \\ \\\\,`\\ \\ ");
    sLog.outString("             \\ \\  \\__/\\  \\ \\ \\ \\  \\__/\\  \\ \\  \\_\\  \\ \\ \\,\\,  \\ ");
    sLog.outString("              \\ \\  \\ \\ \\  \\ \\_\\ \\  \\_\\_\\  \\ \\,____,/\\ \\_\\\\ \\,_\\ ");
    sLog.outString("               \\ \\__\\ \\ \\__\\/_/\\ \\________/\\/_____/  \\/_/ \\/__/ ");
    sLog.outString("                \\/__/  \\/__/    \\/_______/  %s  ", _PROJECTPAGE);
    sLog.outString("             ________   _______  ________   _______ ");
    sLog.outString("            /\\   ____\\/\\`  __  \\/\\   ___ `\\/\\  ____\\ ");
    sLog.outString("            \\ \\  \\___/\\ \\  \\/\\  \\ \\  \\_/\\  \\ \\ \\___/ ");
    sLog.outString("             \\ \\  \\    \\ \\  \\ \\  \\ \\  \\\\/  /\\ \\  __\\ ");
    sLog.outString("              \\ \\  \\    \\ \\  \\ \\  \\ \\   __, `\\ \\ \\_/___ ");
    sLog.outString("               \\ \\  \\    \\ \\,____,/\\ \\  \\_/\\  \\ \\______\\ ");
    sLog.outString("                \\ \\  \\    \\/_____/  \\ \\__\\\\ \\__\\/______/ ");
    sLog.outString("                 \\ \\  \\     WORLD    \\/__/ \\/__/ %s ", _PROJECTYEARS);
    sLog.outString("                  \\ \\  \\______________________________________ ");
    sLog.outString("                   \\ \\______ Codename: %s   ____\\ ", _CODENAME);
    sLog.outString("                    \\/________________________________________/ ");
    sLog.outString();

#ifdef USE_SFMT_FOR_RNG
    sLog.outString( "\n");
    sLog.outString( "SFMT has been enabled as the random number generator, if worldserver");
    sLog.outString( "freezes or crashes randomly, first, try disabling SFMT in CMAKE configuration");
    sLog.outString( "\n");
#endif //USE_SFMT_FOR_RNG

    ///- Get the realm Id from the configuration file
    realmID = 0;
    if (QueryResult qResult = ConfigDatabase.Query("SELECT value FROM 01_connection_and_directories WHERE name='RealmID'"))
    {
        Field *fields = qResult->Fetch();
        if (fields[0].GetUInt32() != realmID)
            realmID = fields[0].GetUInt32();
    }

    if (!realmID)
    {
        sLog.outError("Realm ID not defined in Config DB");
        return false;
    }
    sLog.outString("Realm running as realm ID %d", realmID);

    ///- Initialize the DB logging system

    sLog.SetLogDBLater(sConfigDB.getBoolConfig(CONFIG_LOG_DB_ENABLE)); // set var to enable DB logging once startup finished.
    sLog.SetLogDB(false);
    sLog.SetRealmID(realmID);

    ///- Clean the database before starting
    clearOnlineAccounts();

    ///- Insert version info into DB
    WorldDatabase.PExecute("UPDATE world_version SET core_version = '%s', core_revision = '%s'", _FULLVERSION, _REVISION);

    sWorld.LoadDBVersion();

    sLog.outString("Using World DB: %s", sWorld.GetDBVersion());
    sLog.outString("Using creature EventAI: %s", sWorld.GetCreatureEventAIVersion());

    ///- Initialize the World
    sWorld.SetInitialWorldSettings();

    /// world PID file creation
    std::string pidfile = sWorld.string_pid_file;

    if (!pidfile.empty())
    {
        uint32 pid = CreatePIDFile(pidfile);

        if (!pid)
        {
            sLog.outError("Cannot create PID file %s.\n", pidfile.c_str());
            return 1;
        }

        sLog.outString("Daemon process ID: %u\n", pid);
    }

    // Initialise the signal handlers
    CoredSignalHandler SignalINT, SignalTERM;
#ifdef _WIN32
    CoredSignalHandler SignalBREAK;
#endif /* _WIN32 */

    // Register realmd's signal handlers
    ACE_Sig_Handler Handler;
    Handler.register_handler(SIGINT, &SignalINT);
    Handler.register_handler(SIGTERM, &SignalTERM);
#ifdef _WIN32
    Handler.register_handler(SIGBREAK, &SignalBREAK);
#endif /* _WIN32 */

    ///- Launch WorldRunnable thread
    ACE_Based::Thread world_thread(new WorldRunnable);
    world_thread.setPriority(ACE_Based::Highest);

    // set server online
    LoginDatabase.PExecute("UPDATE realmlist SET color = 0, population = 0 WHERE id = '%d'",realmID);

    ACE_Based::Thread* cliThread = NULL;

#ifdef _WIN32
    if (sWorld.getBoolConfig(CONFIG_CONSOLE_ENABLED) && (m_ServiceStatus == -1)/* need disable console in service mode*/)
#else
    if (sWorld.getBoolConfig(CONFIG_CONSOLE_ENABLED))
#endif
    {
        ///- Launch CliRunnable thread
        cliThread = new ACE_Based::Thread(new CliRunnable);
    }

    ACE_Based::Thread rar_thread(new RARunnable);

    ///- Handle affinity for multiple processors and process priority on Windows
#ifdef _WIN32
    {
        HANDLE hProcess = GetCurrentProcess();
        uint32 Aff = sWorld.getIntConfig(CONFIG_USE_PROCCESSORS);

        if (Aff > 0)
        {
            ULONG_PTR appAff;
            ULONG_PTR sysAff;

            if (GetProcessAffinityMask(hProcess, &appAff, &sysAff))
            {
                ULONG_PTR curAff = Aff & appAff;            // remove non accessible processors

                if (!curAff)
                {
                    sLog.outError("Processors marked in UseProcessors bitmask (hex) %x not accessible for WorldServer. Accessible processors bitmask (hex): %x", Aff, appAff);
                }
                else
                {
                    if (SetProcessAffinityMask(hProcess, curAff))
                        sLog.outString("Using processors (bitmask, hex): %x", curAff);
                    else
                        sLog.outError("Can't set used processors (hex): %x", curAff);
                }
            }
            sLog.outString();
        }

        if(sWorld.getBoolConfig(CONFIG_PROCESS_PRIORITY))
        {
            if (SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS))
                sLog.outString("WorldServer process priority class set to HIGH");
            else
                sLog.outError("Can't set WorldServer process priority class.");
            sLog.outString();
        }
    }
#endif
    //Start soap serving thread
    ACE_Based::Thread* soap_thread = NULL;

    bool soap_enabled   = sWorld.getBoolConfig(CONFIG_SOAP_ENABLED);
    std::string soap_ip = sWorld.string_soap_ip;
    uint32 soap_port    = sWorld.getIntConfig(CONFIG_SOAP_PORT);

    if(soap_enabled)
    {
        RCSoapRunnable *runnable = new RCSoapRunnable();
        runnable->setListenArguments(soap_ip, soap_port);
        soap_thread = new ACE_Based::Thread(runnable);
    }

    uint32 realCurrTime, realPrevTime;
    realCurrTime = realPrevTime = getMSTime();

    ///- Start up freeze catcher thread
    uint32 freeze_delay = sWorld.getIntConfig(CONFIG_MAX_CORE_STACK_TIME);
    if(freeze_delay)
    {
        FreezeDetectorRunnable *fdr = new FreezeDetectorRunnable();
        fdr->SetDelayTime(freeze_delay * 1000);
        ACE_Based::Thread freeze_thread(fdr);
        freeze_thread.setPriority(ACE_Based::Highest);
    }

    ///- Launch the world listener socket
    uint16 wsport = sWorld.getIntConfig(CONFIG_PORT_WORLD);
    std::string bind_ip = sWorld.string_bind_ip;

    if (sWorldSocketMgr->StartNetwork(wsport, bind_ip.c_str()) == -1)
    {
        sLog.outError ("Failed to start network");
        World::StopNow(ERROR_EXIT_CODE);
        // go down and shutdown the server
    }

    sWorldSocketMgr->Wait();

    if(soap_thread)
    {
        soap_thread->wait();
        soap_thread->destroy();
        delete soap_thread;
    }

    // set server offline
    LoginDatabase.PExecute("UPDATE realmlist SET color = 2 WHERE id = '%d'", realmID);

    // when the main thread closes the singletons get unloaded
    // since worldrunnable uses them, it will crash if unloaded after master
    world_thread.wait();
    rar_thread.wait();

    ///- Clean database before leaving
    clearOnlineAccounts();

    ///- Wait for delay threads to end
    CharacterDatabase.Close();
    ConfigDatabase.Close();
    WorldDatabase.Close();
    LoginDatabase.Close();

    sLog.outString("Halting process...");

    if (cliThread)
    {
#ifdef _WIN32
        // this only way to terminate CLI thread exist at Win32 (alt. way exist only in Windows Vista API)
        //_exit(1);
        // send keyboard input to safely unblock the CLI thread
        INPUT_RECORD b[5];
        HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
        b[0].EventType = KEY_EVENT;
        b[0].Event.KeyEvent.bKeyDown = TRUE;
        b[0].Event.KeyEvent.uChar.AsciiChar = 'X';
        b[0].Event.KeyEvent.wVirtualKeyCode = 'X';
        b[0].Event.KeyEvent.wRepeatCount = 1;

        b[1].EventType = KEY_EVENT;
        b[1].Event.KeyEvent.bKeyDown = FALSE;
        b[1].Event.KeyEvent.uChar.AsciiChar = 'X';
        b[1].Event.KeyEvent.wVirtualKeyCode = 'X';
        b[1].Event.KeyEvent.wRepeatCount = 1;

        b[2].EventType = KEY_EVENT;
        b[2].Event.KeyEvent.bKeyDown = TRUE;
        b[2].Event.KeyEvent.dwControlKeyState = 0;
        b[2].Event.KeyEvent.uChar.AsciiChar = '\r';
        b[2].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
        b[2].Event.KeyEvent.wRepeatCount = 1;
        b[2].Event.KeyEvent.wVirtualScanCode = 0x1c;

        b[3].EventType = KEY_EVENT;
        b[3].Event.KeyEvent.bKeyDown = FALSE;
        b[3].Event.KeyEvent.dwControlKeyState = 0;
        b[3].Event.KeyEvent.uChar.AsciiChar = '\r';
        b[3].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
        b[3].Event.KeyEvent.wVirtualScanCode = 0x1c;
        b[3].Event.KeyEvent.wRepeatCount = 1;
        DWORD numb;
        WriteConsoleInput(hStdIn, b, 4, &numb);

        cliThread->wait();
#else
        cliThread->destroy();
#endif

        delete cliThread;
    }

    // Exit the process with specified return value
    return World::GetExitCode();
}

/// Initialize connection to the databases
bool Master::_StartDB()
{
    std::string dbstring;
    uint8 async_threads, synch_threads;

    dbstring = sConfig.GetStringDefault("WorldDatabaseInfo", "");
    if (dbstring.empty())
    {
        sLog.outError("WorldDatabaseInfo not specified in configuration file");
        return false;
    }

    async_threads = sConfig.GetIntDefault("WorldDatabase.WorkerThreads", 1);
    if (async_threads < 1 || async_threads > 32)
    {
        sLog.outError("World database: invalid number of worker threads specified. "
            "Please pick a value between 1 and 32.");
        return false;
    }

    synch_threads = sConfig.GetIntDefault("WorldDatabase.SynchThreads", 1);

    ///- Initialise the world database
    if (!WorldDatabase.Open(dbstring, async_threads, synch_threads))
    {
        sLog.outError("Cannot connect to world database %s", dbstring.c_str());
        return false;
    }
            
    ///- Get character database info from configuration file
    dbstring = sConfig.GetStringDefault("CharacterDatabaseInfo", "");
    if (dbstring.empty())
    {
        sLog.outError("Character database not specified in configuration file");
        return false;
    }

    async_threads = sConfig.GetIntDefault("CharacterDatabase.WorkerThreads", 1);
    if (async_threads < 1 || async_threads > 32)
    {
        sLog.outError("Character database: invalid number of worker threads specified. "
            "Please pick a value between 1 and 32.");
        return false;
    }

    synch_threads = sConfig.GetIntDefault("CharacterDatabase.SynchThreads", 2);

    ///- Initialise the Character database
    if (!CharacterDatabase.Open(dbstring, async_threads, synch_threads))
    {
        sLog.outError("Cannot connect to Character database %s", dbstring.c_str());
        return false;
    }
    
    ///- Get config database info from configuration file
    dbstring = sConfig.GetStringDefault("ConfigDatabaseInfo", "");
    if (dbstring.empty())
    {
        sLog.outError("Config database not specified in configuration file");
        return false;
    }

    async_threads = sConfig.GetIntDefault("ConfigDatabase.WorkerThreads", 1);
    if (async_threads < 1 || async_threads > 32)
    {
        sLog.outError("Config database: invalid number of worker threads specified. "
            "Please pick a value between 1 and 32.");
        return false;
    }

    synch_threads = sConfig.GetIntDefault("ConfigDatabase.SynchThreads", 2);

    ///- Initialise the Config database
    if (!ConfigDatabase.Open(dbstring, async_threads, synch_threads))
    {
        sLog.outError("Cannot connect to Config database %s", dbstring.c_str());
        return false;
    }
    
    ///- Get login database info from configuration file
    dbstring = sConfig.GetStringDefault("LoginDatabaseInfo", "");
    if (dbstring.empty())
    {
        sLog.outError("LoginDatabaseInfo not specified in configuration file");
        return false;
    }

    async_threads = sConfig.GetIntDefault("LoginDatabase.WorkerThreads", 1);
    if (async_threads < 1 || async_threads > 32)
    {
        sLog.outError("Login database: invalid number of worker threads specified. "
            "Please pick a value between 1 and 32.");
        return false;
    }

    synch_threads = sConfig.GetIntDefault("LoginDatabase.SynchThreads", 1);

    ///- Initialise the login database
    if (!LoginDatabase.Open(dbstring, async_threads, synch_threads))
    {
        sLog.outError("Cannot connect to login database %s", dbstring.c_str());
        return false;
    }

    return true;
}

/// Clear 'online' status for all accounts with characters in this realm
void Master::clearOnlineAccounts()
{
    // Cleanup online status for characters hosted at current realm
    /// \todo Only accounts with characters logged on *this* realm should have online status reset. Move the online column from 'account' to 'realmcharacters'?
    LoginDatabase.PExecute(
        "UPDATE account SET online = 0 WHERE online > 0 "
        "AND id IN (SELECT acctid FROM realmcharacters WHERE realmid = '%d')", realmID);

    CharacterDatabase.Execute("UPDATE characters SET online = 0 WHERE online<>0");

    // Battleground instance ids reset at server restart
    CharacterDatabase.Execute("UPDATE character_battleground_data SET instance_id = 0");
}
